/*
 * [y] hybris Platform
 *
 * Copyright (c) 2000-2017 SAP SE
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of SAP
 * Hybris ("Confidential Information"). You shall not disclose such
 * Confidential Information and shall use it only in accordance with the
 * terms of the license agreement you entered into with SAP Hybris.
 */
package acc.asset.sync;

import de.hybris.platform.core.PK;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.task.TaskConditionModel;
import de.hybris.platform.task.TaskModel;
import de.hybris.platform.task.TaskService;
import de.hybris.y2ysync.enums.Y2YSyncType;
import de.hybris.y2ysync.task.internal.MediasForType;

import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import com.google.common.collect.ImmutableMap;


/**
 *
 */
public class DemoSyncTaskFactory // SyncTaskFactory
{
	private final static Logger LOG = Logger.getLogger(DemoSyncTaskFactory.class);
	private ModelService modelService;
	private TaskService taskService;

	//@Override
	public void runSyncTasks(final String syncExecutionID, final Y2YSyncType syncType, final List<MediasForType> mediaForType,
			final String nodeGroup)
	{
		final Set<TaskConditionModel> conditions = this.prepareAndScheduleTasks(syncExecutionID, syncType, mediaForType, nodeGroup);
		if (conditions.isEmpty())
		{
			LOG.info("No changes detected - no import tasks will be executed.");
			return;
		}
		this.prepareAndScheduleTaskOnConditions(this.getMainTaskRunnerBeanId(syncType), this.getMainTaskContext(syncExecutionID),
				conditions, nodeGroup);
	}

	private Set<TaskConditionModel> prepareAndScheduleTasks(final String syncExecutionId, final Y2YSyncType syncType,
			final List<MediasForType> mediasPerType, final String nodeGroup)
	{
		final HashSet<TaskConditionModel> conditions = new HashSet<TaskConditionModel>(mediasPerType.size());

		for (final MediasForType mediasForType : mediasPerType)
		{
			if (mediasForType.getMediaPks() == null || mediasForType.getMediaPks().isEmpty())
			{
				this.prepareAndScheduleTaskNow("itemChangesProcessor",
						this.getChunkTaskContext(syncExecutionId, syncType, null, mediasForType), nodeGroup);

				conditions.add(prepareTaskCondition(syncExecutionId, null, mediasForType));
			}
			else
			{
				for (final PK mediaPK : mediasForType.getMediaPks())
				{
					this.prepareAndScheduleTaskNow("itemChangesProcessor",
							this.getChunkTaskContext(syncExecutionId, syncType, mediaPK, mediasForType), nodeGroup);

					conditions.add(prepareTaskCondition(syncExecutionId, mediaPK, null));
				}
			}

		}

		return conditions;
	}

	private Map<String, Object> getChunkTaskContext(final String syncExecutionId, final Y2YSyncType syncType, final PK mediaPK,
			final MediasForType mediasForType)
	{
		final ImmutableMap.Builder result = this.getTaskContextBuilder(syncExecutionId)
				.put("conditionName", createConditionName(syncExecutionId, mediaPK, mediasForType))
				.put("impexHeader", mediasForType.getImpexHeader()).put("typeCode", mediasForType.getComposedTypeCode())
				.put("syncType", syncType);

		if (mediaPK != null)
		{
			result.put("mediaPK", mediaPK);
		}

		if (StringUtils.isNotBlank(mediasForType.getDataHubColumns()))
		{
			result.put("dataHubColumns", mediasForType.getDataHubColumns()).build();
		}

		if (StringUtils.isNotBlank(mediasForType.getDataHubType()))
		{
			result.put("dataHubType", mediasForType.getDataHubType()).build();
		}

		return result.build();
	}

	private String createConditionName(final String syncExecutionId, final PK mediaPK, final MediasForType mediasForType)
	{
		String conditionName = "";
		if (mediaPK == null)
		{
			conditionName = syncExecutionId + "_"
					+ (mediasForType == null ? String.valueOf(System.currentTimeMillis()) : mediasForType.getComposedTypeCode());
		}
		else
		{
			conditionName = syncExecutionId + "_" + mediaPK;
		}

		return conditionName;
	}

	private Map<String, Object> getMainTaskContext(final String syncExecutionId)
	{
		return this.getTaskContextBuilder(syncExecutionId).build();
	}

	private ImmutableMap.Builder<String, Object> getTaskContextBuilder(final String syncExecutionId)
	{
		return ImmutableMap.<String, Object> builder().put("syncExecutionID", syncExecutionId);
	}

	private void prepareAndScheduleTaskNow(final String runnerBeanID, final Map<String, Object> ctx, final String nodeGroup)
	{
		final TaskModel task = this.prepareTask(runnerBeanID, ctx, nodeGroup);
		task.setExecutionDate(new Date());
		this.taskService.scheduleTask(task);
	}

	private void prepareAndScheduleTaskOnConditions(final String runnerBeanID, final Map<String, Object> ctx,
			final Set<TaskConditionModel> conditions, final String nodeGroup)
	{
		final TaskModel task = this.prepareTask(runnerBeanID, ctx, nodeGroup);
		task.setConditions(conditions);
		this.taskService.scheduleTask(task);
	}

	private TaskModel prepareTask(final String runnerBeanID, final Map<String, Object> ctx, final String nodeGroup)
	{
		final TaskModel task = (TaskModel) this.modelService.create(TaskModel.class);
		task.setContext(ctx);
		task.setRunnerBean(runnerBeanID);
		task.setNodeGroup(nodeGroup);
		return task;
	}


	private TaskConditionModel prepareTaskCondition(final String syncExecutionId, final PK mediaPK,
			final MediasForType mediasForType)
	{
		final TaskConditionModel taskCondition = (TaskConditionModel) this.modelService.create(TaskConditionModel.class);
		final String conditionUID = createConditionName(syncExecutionId, mediaPK, mediasForType);
		LOG.debug("Setting up UID for chunk task condition: {" + conditionUID + "}");
		taskCondition.setUniqueID(conditionUID);
		return taskCondition;
	}

	private String getMainTaskRunnerBeanId(final Y2YSyncType syncType)
	{
		if (Y2YSyncType.ZIP == syncType)
		{
			return "syncZipTaskRunner";
		}
		if (Y2YSyncType.DATAHUB == syncType)
		{
			return "syncDataHubTaskRunner";
		}
		throw new IllegalStateException("Y2YSyncType must be choosed");
	}

	/**
	 * @return the modelService
	 */
	public ModelService getModelService()
	{
		return modelService;
	}

	/**
	 * @param modelService
	 *           the modelService to set
	 */
	//@Override
	public void setModelService(final ModelService modelService)
	{
		//super.setModelService(modelService);
		this.modelService = modelService;
	}

	/**
	 * @return the taskService
	 */
	public TaskService getTaskService()
	{
		return taskService;
	}

	/**
	 * @param taskService
	 *           the taskService to set
	 */
	//@Override
	public void setTaskService(final TaskService taskService)
	{
		//super.setTaskService(taskService);
		this.taskService = taskService;
	}
}
